package com.harmontronics.hanma.paas.paas.domain.tenant.service;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.harmontronics.hanma.paas.paas.common.PageBase;
import com.harmontronics.hanma.paas.paas.consts.DbConstants;
import com.harmontronics.hanma.paas.paas.consts.NumberEnum;
import com.harmontronics.hanma.paas.paas.domain.tenant.consts.HmTenantAppFields;
import com.harmontronics.hanma.paas.paas.domain.tenant.consts.HmTenantAppManagerFields;
import com.harmontronics.hanma.paas.paas.domain.tenant.consts.HmTenantUserFields;
import com.harmontronics.hanma.paas.paas.domain.tenant.entity.AppManager;
import com.harmontronics.hanma.paas.paas.domain.tenant.entity.HmTenantApp;
import com.harmontronics.hanma.paas.paas.domain.tenant.entity.TenantAppTree;
import com.harmontronics.hanma.paas.paas.domain.tenant.entity.TenantUser;
import com.harmontronics.hanma.paas.paas.domain.tenant.entity.valueobject.I18nKeyEnum;
import com.harmontronics.hanma.paas.paas.domain.tenant.repository.mapper.HmTenantAppManagerMapper;
import com.harmontronics.hanma.paas.paas.domain.tenant.repository.mapper.HmTenantAppMapper;
import com.harmontronics.hanma.paas.paas.domain.tenant.repository.persistence.HmTenantAppManagerRepository;
import com.harmontronics.hanma.paas.paas.domain.tenant.repository.persistence.HmTenantAppRepository;
import com.harmontronics.hanma.paas.paas.domain.tenant.repository.persistence.HmTenantUserRepository;
import com.harmontronics.hanma.paas.paas.domain.tenant.repository.po.HmTenantAppManagerPO;
import com.harmontronics.hanma.paas.paas.domain.tenant.repository.po.HmTenantAppPO;
import com.harmontronics.hanma.paas.paas.domain.tenant.repository.po.HmTenantUserPO;
import com.harmontronics.hanma.paas.paas.exception.ErrorKeyException;
import com.harmontronics.hanma.paas.paas.token.TenantUserTokenKit;
import com.harmontronics.hanma.paas.paas.token.UserInfo;
import com.harmontronics.hanma.paas.paas.utils.GenerateUniqueId;
import com.harmontronics.hanma.paas.paas.utils.StringUtil;
import org.apache.ibatis.jdbc.ScriptRunner;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ObjectUtils;

import javax.sql.DataSource;
import java.io.IOException;
import java.io.InputStreamReader;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

@Service("hmTenantAppService")
public class HmTenantAppService {

    @Autowired
    private HmTenantAppRepository hmTenantAppRepository;

    @Autowired
    private HmTenantAppManagerRepository hmTenantAppManagerRepository;

    @Autowired
    private HmTenantUserRepository hmTenantUserRepository;

    @Autowired
    private HmTenantAppManagerMapper hmTenantAppManagerMapper;

    @Autowired
    private HmTenantAppMapper hmTenantAppMapper;
    @Autowired
    private TenantUserTokenKit tenantUserTokenKit;

    @Autowired
    private DynamicDataSourceService dynamicDataSourceService;


    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    private String TENANT_APP_LOCK_PREFIX = "TENANT:APP:LOCK:";


    public HmTenantApp findById(Long id) {
        UserInfo userInfo = tenantUserTokenKit.getUserInfo();
        Long currentUserId = userInfo.getUserId();
        Long currentTenantId = userInfo.getTenantId();
        HmTenantAppPO po = hmTenantAppRepository.getById(id);
        if (!Optional.ofNullable(po).isPresent()) {
            throw new ErrorKeyException(I18nKeyEnum.REQUEST_PARAM_ERROR);
        }
        HmTenantApp app = BeanUtil.toBean(po, HmTenantApp.class);
        //查询该应用的所有管理员
        List<AppManager> managerDtoList = hmTenantAppManagerMapper.getManagersByAppId(id);
        //获取当前租户的管理员ID
        Long adminId = getAdminId(currentTenantId);
        List<AppManager> result = managerDtoList.stream().filter(Objects::nonNull).
                filter((AppManager dto) -> dto.getId().longValue() != adminId.longValue()
                        && dto.getId().longValue() != po.getCreateId().longValue()).collect(
                Collectors.toList());
        app.setManagerList(result);
        return app;
    }

    public List<TenantAppTree> getAllApp(String token, int deleted) {
        UserInfo userInfo = tenantUserTokenKit.getUserInfo();
        Long currentUserId = userInfo.getUserId();
        Long currentTenantId = userInfo.getTenantId();
        Long adminId = getAdminId(currentTenantId);
        List<TenantAppTree> treeDtoList = new ArrayList<>();
        if(currentUserId.equals(adminId)){
            treeDtoList = hmTenantAppMapper.getAllApp(currentTenantId, currentUserId, deleted);
        }else{
            treeDtoList = hmTenantAppMapper.getAppByTenantUserId(currentTenantId, currentUserId, deleted);
        }

         treeDtoList.forEach(treeDto -> {
                Long appId = treeDto.getId();
                List<AppManager> managerDtoList = hmTenantAppManagerMapper.getManagersByAppId(appId);
                if(CollectionUtil.isNotEmpty(managerDtoList)){
                    List<AppManager> result = managerDtoList.stream().
                            filter((AppManager dto) -> dto != null && !dto.getId().equals(currentUserId) && !dto.getId().equals(adminId)).collect(
                            Collectors.toList());
                    treeDto.setManagerList(result);
                }
         });

        return treeDtoList;
    }

    /**
     * 应用保存
     *
     * @param hmTenantApp
     */
    @Transactional(rollbackFor = Exception.class)
    public void save(HmTenantApp hmTenantApp, String token) {
        UserInfo userInfo = tenantUserTokenKit.getUserInfo();
        Long currentId = userInfo.getUserId();
        //根据当前用户的id去获取租户和用户的信息
        TenantUser dto = validateUserId(currentId, hmTenantApp.getManagerIds());
        List<Long> managerIdList = dto.getManagerIdList();
        Long tenantId = userInfo.getTenantId();

        HmTenantAppPO po = BeanUtil.toBean(hmTenantApp, HmTenantAppPO.class);
        po.setAppUniqueId(generateUniqueId());
        po.setTenantId(tenantId);
        po.setAppType(NumberEnum.TWO.getNum());
        po.setCreateId(currentId);
        hmTenantAppRepository.save(po);
        hmTenantApp.setAppUniqueId(po.getAppUniqueId());
        if (CollectionUtil.isNotEmpty(managerIdList)) {
            managerIdList.forEach(id -> {
                HmTenantAppManagerPO managerPo = new HmTenantAppManagerPO();
                managerPo.setTenantAppId(po.getId());
                managerPo.setTenantId(po.getTenantId());
                managerPo.setTenantUserId(id);
                hmTenantAppManagerRepository.save(managerPo);
            });
        }

        initDatabase(po.getAppUniqueId());
    }

    String generateUniqueId() {
        String uniqueId = GenerateUniqueId.generateId();
        boolean exist = hmTenantAppRepository.appUniqueIdExists(uniqueId);
        if (!exist) {
            return uniqueId;
        }
        return generateUniqueId();
    }

    /**
     * 初始化 应用数据库
     *
     * @param uniqueId
     */
    private void initDatabase(String uniqueId) {
        String dbName = "app_" + uniqueId;
        String dsName = "hm_" + uniqueId;
        createDb(dbName);
        addDataSource(dbName, dsName);

    }

    /**
     * 创建数据源 添加到动态路由中
     *
     * @param dbName
     */
    private void addDataSource(String dbName, String dsName) {
        DataSource dataSource = dynamicDataSourceService.createDataSource(dsName,dbName);
        dynamicDataSourceService.addDataSource(dsName, dataSource);
        try {
            Connection connection = dataSource.getConnection();
            executeSql(connection);
            connection.close();
        } catch (Exception ex) {
            dynamicDataSourceService.removeDataSource(dsName);
            throw new ErrorKeyException(I18nKeyEnum.APP_DB_INIT_ERROR);
        }

    }

    /**
     * 创建数据库
     *
     * @param dbName
     */
    private void createDb(String dbName) {
        try {
            DataSource dataSource = dynamicDataSourceService.getDataSource(DbConstants.DS_MASTER_NAME);
            Connection connection = dataSource.getConnection();
            String sql = "create database " + dbName;
            PreparedStatement ps = connection.prepareStatement(sql);
            ps.execute();
            connection.close();
        } catch (Exception ex) {
            ex.printStackTrace();
            throw new ErrorKeyException(I18nKeyEnum.APP_DB_INIT_ERROR);
        }
    }

    /**
     * 执行初始化脚本
     *
     * @param connection
     * @throws IOException
     */
    private void executeSql(Connection connection) throws IOException {
        ScriptRunner runner = new ScriptRunner(connection);
        runner.setAutoCommit(true);
        runner.setFullLineDelimiter(false);
        runner.setDelimiter(";");
        runner.setSendFullScript(false);
        runner.setStopOnError(true);
        runner.runScript(new InputStreamReader(new ClassPathResource("schema/app_db_init_v1.0.sql").getInputStream()));
        runner.runScript(new InputStreamReader(new ClassPathResource("schema/app_db_init_v1.0_1.sql").getInputStream()));
        runner.runScript(new InputStreamReader(new ClassPathResource("schema/app_db_init_v1.0_2.sql").getInputStream()));
        runner.runScript(new InputStreamReader(new ClassPathResource("schema/app_db_init_v1.1.sql").getInputStream()));
    }

    /**
     * 更新应用
     *
     * @param hmTenantApp
     * @param token
     */
    public void update(HmTenantApp hmTenantApp, String token) {
        Long currentId = tenantUserTokenKit.getId();
        validateManager(currentId, hmTenantApp.getId());

        TenantUser tenantUserDto = validateUserId(currentId, hmTenantApp.getManagerIds());
        List<Long> managerIdList = tenantUserDto.getManagerIdList();
        Long tenantId = tenantUserDto.getTenantId();
        HmTenantAppPO po = BeanUtil.toBean(hmTenantApp, HmTenantAppPO.class);
        hmTenantAppRepository.updateById(po);
        updateTenantManager(tenantId, po.getId(), managerIdList);
    }

    /**
     * 更新应用的管理员
     *
     * @param tenantId
     * @param tenantAppId
     * @param managerIdList
     */
    private void updateTenantManager(Long tenantId, Long tenantAppId, List<Long> managerIdList) {
        QueryWrapper<HmTenantAppManagerPO> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq(DbConstants.TENANT_ID, tenantId)
                .eq(HmTenantAppManagerFields.TENANT_APP_ID, tenantAppId);
        hmTenantAppManagerRepository.remove(queryWrapper);
        if (CollectionUtil.isNotEmpty(managerIdList)) {
            managerIdList.forEach(id -> {
                HmTenantAppManagerPO managerPO = new HmTenantAppManagerPO();
                managerPO.setTenantAppId(tenantAppId);
                managerPO.setTenantId(tenantId);
                managerPO.setTenantUserId(id);
                hmTenantAppManagerRepository.save(managerPO);
            });
        }
    }

    /**
     * 应用文件夹保存
     *
     * @param hmTenantApp
     */
    public void saveFolder(HmTenantApp hmTenantApp, String token) {
        Long currentUserId = tenantUserTokenKit.getId();
        TenantUser userDto = validateUserId(currentUserId, hmTenantApp.getManagerIds());
        Long tenantId = userDto.getTenantId();
        List<Long> managerIdList = userDto.getManagerIdList();

        HmTenantAppPO po = BeanUtil.toBean(hmTenantApp, HmTenantAppPO.class);
        po.setTenantId(tenantId);
        po.setAppUniqueId(GenerateUniqueId.generateId());
        po.setAppType(NumberEnum.ONE.getNum());
        po.setCreateId(currentUserId);
        hmTenantAppRepository.save(po);
        if (CollectionUtil.isNotEmpty(managerIdList)) {
            managerIdList.forEach(id -> {
                HmTenantAppManagerPO managerPO = new HmTenantAppManagerPO();
                managerPO.setTenantAppId(po.getId());
                managerPO.setTenantId(po.getTenantId());
                managerPO.setTenantUserId(id);
                hmTenantAppManagerRepository.save(managerPO);
            });
        }
    }

    /**
     * 更新文件夹
     *
     * @param hmTenantApp
     * @param token
     */
    public void updateFolder(HmTenantApp hmTenantApp, String token) {
        Long currentUserId = tenantUserTokenKit.getId();
        validateManager(currentUserId, hmTenantApp.getId());

        TenantUser tenantUser = validateUserId(currentUserId, hmTenantApp.getManagerIds());
        List<Long> managerIdList = tenantUser.getManagerIdList();
        Long tenantId = tenantUser.getTenantId();
        HmTenantAppPO po = BeanUtil.toBean(hmTenantApp, HmTenantAppPO.class);
        hmTenantAppRepository.updateById(po);
        updateTenantManager(tenantId, po.getId(), managerIdList);
    }


    /**
     * 删除应用
     *
     * @param ids
     */
    public void remove(List<Long> ids, String token) {
        Long currentId = tenantUserTokenKit.getId();
        ids.forEach(id -> {
            validateManager(currentId, id);
            HmTenantAppPO hmTenantAppPO = hmTenantAppRepository.getById(id);
            if (!Optional.ofNullable(hmTenantAppPO).isPresent()) {
                if (hmTenantAppPO.getStatus() == NumberEnum.ONE.getNum() ||
                        hmTenantAppPO.getReleased() == NumberEnum.ONE.getNum()) {
                    throw new ErrorKeyException(I18nKeyEnum.REQUEST_PARAM_ERROR);
                }
            }
        });
        UserInfo userInfo = tenantUserTokenKit.getUserInfo();
        ;
        UpdateWrapper updateWrapper = new UpdateWrapper();
        updateWrapper.in(HmTenantUserFields.ID, ids);
        updateWrapper.set(HmTenantUserFields.DELETED, NumberEnum.ONE.getNum());
        updateWrapper.set(HmTenantAppFields.MODIFY_BY, userInfo.getUserName());
        updateWrapper.set(HmTenantAppFields.MODIFY_DATE, LocalDateTime.now());
        hmTenantAppRepository.update(updateWrapper);
    }

    /**
     * 删除文件夹
     *
     * @param idList
     */
    public void removeFolder(List<Long> idList, String token) {
        Long currentId = tenantUserTokenKit.getId();
        idList.forEach(id -> {
            validateManager(currentId, id);
            QueryWrapper<HmTenantAppPO> queryWrapper = new QueryWrapper<>();
            queryWrapper.eq(HmTenantAppFields.PARENT_ID, id);
            queryWrapper.eq(DbConstants.DELETED, NumberEnum.ZERO.getNum());
            List<HmTenantAppPO> appList = hmTenantAppRepository.list(queryWrapper);
            if (CollectionUtil.isNotEmpty(appList)) {
                throw new ErrorKeyException(I18nKeyEnum.APP_FOLDER_NOT_EMPTY);
            } else {
                HmTenantAppPO hmTenantAppPO = new HmTenantAppPO();
                hmTenantAppPO.setId(id);
                hmTenantAppPO.setDeleted(NumberEnum.ONE.getNum());
                hmTenantAppRepository.updateById(hmTenantAppPO);
            }
        });
    }


    public PageBase<HmTenantApp> getList(int pageNo, int pageSize, String appName, String tenantName,
                                         String startTime, String endTime, int deleted, int released) {
        Page<HmTenantAppPO> page = new Page<>(pageNo, pageSize);
        IPage<HmTenantAppPO> resultPage = hmTenantAppMapper.selectAppListPage(page, appName, tenantName, startTime, endTime, deleted, released);
        return PageBase.from(resultPage, HmTenantApp.class);
    }


    public PageBase<HmTenantApp> getListBin(int pageNo, int pageSize, String appName) {
        Long currentUserId = tenantUserTokenKit.getId();
        Page<HmTenantAppPO> page = new Page<>(pageNo, pageSize);
        IPage<HmTenantAppPO> resultPage = hmTenantAppMapper.selectAppBinListPage(page, appName, currentUserId);
        return PageBase.from(resultPage, HmTenantApp.class);
    }

    public void recover(Long id) {
        HmTenantAppPO po = hmTenantAppRepository.getById(id);
        po.setDeleted(NumberEnum.ZERO.getNum());
        hmTenantAppRepository.updateById(po);
        Long parentId = po.getParentId();
        if (!parentId.equals(NumberEnum.ZERO.getNumLong())) {
            HmTenantAppPO parent = hmTenantAppRepository.getById(parentId);
            if (Optional.ofNullable(parent).isPresent()) {
                if (parent.getDeleted() == NumberEnum.ONE.getNum()) {
                    parent.setDeleted(NumberEnum.ZERO.getNum());
                    hmTenantAppRepository.updateById(parent);
                }
            }
        }
    }

    /**
     * 更新状态
     *
     * @param hmTenantApp
     */
    public void updateStatus(HmTenantApp hmTenantApp) {
        Long currentId = tenantUserTokenKit.getId();
        validateManager(currentId, hmTenantApp.getId());

        HmTenantAppPO po = hmTenantAppRepository.getById(hmTenantApp.getId());
        po.setStatus(hmTenantApp.getStatus());
        hmTenantAppRepository.updateById(po);
        stringRedisTemplate.opsForValue().set(TENANT_APP_LOCK_PREFIX + po.getAppUniqueId(), hmTenantApp.getStatus().toString());

    }


    public boolean isLocked(String appUnique) {
        String status = stringRedisTemplate.opsForValue().get(TENANT_APP_LOCK_PREFIX + appUnique);
        if (ObjectUtils.isEmpty(status)) {
            HmTenantAppPO po = hmTenantAppRepository.findByAppUnique(appUnique);
            if(po == null){
                return true;
            }
            stringRedisTemplate.opsForValue().set(TENANT_APP_LOCK_PREFIX + po.getAppUniqueId(), po.getStatus().toString());
            return po.getStatus() == NumberEnum.ONE.getNum();
        }
        return "1".equals(status);
    }


    /**
     * 根据当前的用户id，获取租户和用户的信息
     *
     * @param currentUserId
     * @param ids
     * @return
     */
    private TenantUser validateUserId(Long currentUserId, String ids) {
        TenantUser tenantUserDto = new TenantUser();
        UserInfo userInfo = tenantUserTokenKit.getUserInfo();
        ;
        Long tenantId = userInfo.getTenantId();
        QueryWrapper<HmTenantUserPO> hmTenantUserPOQueryWrapper = new QueryWrapper<>();

        //根据租户id查询租户所属的用户
        hmTenantUserPOQueryWrapper.eq(DbConstants.TENANT_ID, tenantId)
                .eq(DbConstants.DELETED, NumberEnum.ZERO.getNum());
        List<HmTenantUserPO> userList = hmTenantUserRepository.list(hmTenantUserPOQueryWrapper);
        List<Long> userIdList = userList.stream().map(HmTenantUserPO::getId).collect(Collectors.toList());
        //判断传入的id是否是该租户下的用户
        List<Long> managerIdList = new ArrayList<>();
        if (StringUtil.isNotBlank(ids)) {
            List<Long> idList = Arrays.stream(ids.split(",")).map(Long::parseLong).collect(
                    Collectors.toList());
            for (int i = 0; i < idList.size(); i++) {
                if (userIdList.contains(idList.get(i))) {
                    managerIdList.add(idList.get(i));
                }
            }
            //如果用户不是租户创立的，抛异常
            if (CollectionUtil.isEmpty(managerIdList)) {
                throw new ErrorKeyException(I18nKeyEnum.REQUEST_PARAM_ERROR);
            }
        }
        //如果当前用户不是admin
        if (userInfo.isAdmin()) {
            List<Long> tmpIdList = userList.stream().filter((HmTenantUserPO po) -> po.getIsAdmin() == 1).
                    map(HmTenantUserPO::getId).collect(Collectors.toList());
            if (CollectionUtil.isNotEmpty(tmpIdList)) {
                managerIdList.add(tmpIdList.get(0));
            }
        }

        managerIdList.add(currentUserId);


        tenantUserDto.setTenantId(tenantId);
        tenantUserDto.setManagerIdList(managerIdList);
        return tenantUserDto;
    }

    /**
     * 验证用户是否是应用的管理员
     */
    private void validateManager(Long currentUserId, Long appId) {
        UserInfo userInfo = tenantUserTokenKit.getUserInfo();
        ;
        Long tenantId = userInfo.getTenantId();
        QueryWrapper<HmTenantAppManagerPO> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq(HmTenantAppManagerFields.TENANT_ID, tenantId).
                eq(HmTenantAppManagerFields.TENANT_APP_ID, appId);
        // 如果当前用户不是管理员
        if (!userInfo.isAdmin()) {
            queryWrapper.eq(HmTenantAppManagerFields.TENANT_USER_ID, currentUserId);
        }
        List<HmTenantAppManagerPO> managerPOList = hmTenantAppManagerRepository.list(queryWrapper);
        if (CollectionUtil.isEmpty(managerPOList)) {
            throw new ErrorKeyException(I18nKeyEnum.REQUEST_PARAM_ERROR);
        }
    }


    private Long getAdminId(Long currentTenantId) {
        QueryWrapper<HmTenantUserPO> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq(HmTenantUserFields.TENANT_ID, currentTenantId).
                eq(HmTenantUserFields.IS_ADMIN, NumberEnum.ONE.getNum()).
                eq(HmTenantUserFields.DELETED, NumberEnum.ZERO.getNum());
        List<HmTenantUserPO> adminList = hmTenantUserRepository.list(queryWrapper);
        if (CollectionUtil.isNotEmpty(adminList)) {
            return adminList.get(0).getId();
        }
        return null;
    }

    public HmTenantApp findByUnique(String appUnique) {
        QueryWrapper<HmTenantAppPO> queryWrapper = new QueryWrapper<HmTenantAppPO>().eq(HmTenantAppFields.APP_UNIQUE_ID, appUnique);
        HmTenantAppPO po = hmTenantAppMapper.selectOne(queryWrapper);
        if (!Optional.ofNullable(po).isPresent()) {
            throw new ErrorKeyException(I18nKeyEnum.REQUEST_PARAM_ERROR);
        }
        HmTenantApp app = BeanUtil.toBean(po, HmTenantApp.class);
        //查询该应用的所有管理员
        List<AppManager> managerDtoList = hmTenantAppManagerMapper.getManagersByAppId(app.getId());
        //获取当前租户的管理员ID

        Long adminId = getAdminId(app.getTenantId());
        if (adminId != null) {
            List<AppManager> result =
                    managerDtoList.stream().filter(Objects::nonNull)
                            .filter(
                                    (AppManager dto) ->
                                            dto.getId().longValue() != adminId.longValue()
                                                    && dto.getId().longValue() != po.getCreateId().longValue())
                            .collect(Collectors.toList());
            app.setManagerList(result);
        }
        return app;
    }


}